/*
   Copyright (C) 2011 Roman Belov <romblv@gmail.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "lang/lexer.h"
#include "util/list.h"
#include "util/link.h"

#define LEXER_LINE_MAX		4096
#define LEXER_EOF		-1

struct lexer_global lexglob;

static FILE *
lexer_fopen_path(const char *file, char *full)
{
	FILE		*fd = NULL;
	link_node_t	*lk;
	const char	*path;

	list_for_each(&lexglob.path_list, lk) {

		path = lk->pl;

		strcpy(full, path);
		strcat(full, "/");
		strcat(full, file);

		fd = fopen(full, "r");

		if (fd) break;
	}

	return fd;
}

struct lexer *
lexer_source_open(const char *file,
		const char *from_file,
		int line)
{
	struct lexer	*lex;
	char		*fname;
	FILE		*fd;

	fname = malloc(256); /* FIXME: In future may happen a memory corruption */

	fd = lexer_fopen_path(file, fname);

	if (fd == NULL) {

		if (from_file) {
			fprintf(stderr, "%s:%i: lexer: failed to find \"%s\" in paths\n",
					from_file, line, file);
		}
		else {
			fprintf(stderr, "global: lexer: failed to find \"%s\" in paths\n",
					file);
		}

		return NULL;
	}

	lex = malloc(sizeof(struct lexer));

	lex->fd = fd;
	lex->fname = fname;

	lex->tok.id = TOKEN_NULL;
	lex->tok.val = NULL;

	lex->buff = malloc(LEXER_LINE_MAX);
	lex->attr = malloc(LEXER_SYM_MAX + 1);

	lex->bp = lex->buff;
	*lex->bp = '\0';

	lex->line = 0;

	lex->btok = 0;
	lex->error = 0;

	return lex;
}

void
lexer_source_close(struct lexer *lex)
{
	if (lex->error) {
		fprintf(stderr, "global: lexer: %d lexical errors was detected\n",
				lex->error);
	}

	fclose(lex->fd);
	free(lex->fname);
	free(lex->buff);
	free(lex->attr);
	free(lex);
}

static inline int
is_space(char c)
{
	return (c == ' ' || c == '\t'
		|| c == '\r' || c == '\n');
}

static inline int
is_char(char c)
{
	return ((c >= 'A' && c <= 'Z')
		|| (c >= 'a' && c <= 'z'));
}

static inline int
is_digit(char c)
{
	return (c >= '0' && c <= '9');
}

static int
lexer_line_fill(struct lexer *lex)
{
	char		*s;

	s = fgets(lex->buff, LEXER_LINE_MAX, lex->fd);

	if (s) {
		++lex->line;
		lex->bp = lex->buff;
	}
	else return 0;

	return 1;
}

static int
lexer_getc(struct lexer *lex)
{
	int		ret;

	if (*lex->bp == '\0') {
		ret = lexer_line_fill(lex);
		if (!ret) return LEXER_EOF;
	}

	return *lex->bp++;
}

static void
lexer_ungetc(struct lexer *lex)
{
	if (*lex->bp != '\0') lex->bp--;
}

static inline void
lexer_safe_store(struct lexer *lex, char *str, char **ptr, char c)
{
	int		len;

	len = (*ptr - str);

	if (len < LEXER_SYM_MAX) {

		/* Store character, it is safe.
		 * */

		*(*ptr)++ = c;
	}
	else if (lex->btok == 0) {

		*(str + 16) = '\0';
		fprintf(stderr, "%s:%i: lexer: token \"%s...\" too long\n",
				lex->fname, lex->line, str);
		lex->btok = 1;
		lex->error++;
	}
}

void
lexer_token_next(struct lexer *lex)
{
	char		*attr = lex->attr;
	char		*p = attr;
	int		c;

	lex->btok = 0;

ltn_start:

	if (is_space(lexer_getc(lex))) {
		lex->btok = 0;
		while (is_space(lexer_getc(lex))) ;
	}

	lexer_ungetc(lex);

	c = lexer_getc(lex);

	if (c == '_' || is_char(c)) {

		while (c == '_' || is_char(c) || is_digit(c)) {
			lexer_safe_store(lex, attr, &p, c);
			c = lexer_getc(lex);
		}

		lexer_ungetc(lex);
		*p++ = '\0';

		if (!strcmp(attr, "use")) {
			lex->tok.id = TOKEN_USE;
			lex->tok.val = NULL;
		}
		else if (!strcmp(attr, "module")) {
			lex->tok.id = TOKEN_MODULE;
			lex->tok.val = NULL;
		}
		else if (!strcmp(attr, "port")) {
			lex->tok.id = TOKEN_PORT;
			lex->tok.val = NULL;
		}
		else if (!strcmp(attr, "place")) {
			lex->tok.id = TOKEN_PLACE;
			lex->tok.val = NULL;
		}
		else if (!strcmp(attr, "link")) {
			lex->tok.id = TOKEN_LINK;
			lex->tok.val = NULL;
		}
		else if (!strcmp(attr, "in")) {
			lex->tok.id = TOKEN_IN;
			lex->tok.val = NULL;
		}
		else if (!strcmp(attr, "out")) {
			lex->tok.id = TOKEN_OUT;
			lex->tok.val = NULL;
		}
		else if (!strcmp(attr, "inout")) {
			lex->tok.id = TOKEN_INOUT;
			lex->tok.val = NULL;
		}
		else if (!strcmp(attr, "lib")) {
			lex->tok.id = TOKEN_LIB;
			lex->tok.val = NULL;
		}
		else if (!strcmp(attr, "specify")) {
			lex->tok.id = TOKEN_SPECIFY;
			lex->tok.val = NULL;
		}
		else {
			lex->tok.id = TOKEN_SYMBOL;
			lex->tok.val = attr;
		}
	}
	else if (is_digit(c)) {

		do {
			lexer_safe_store(lex, attr, &p, c);
			c = lexer_getc(lex);
		}
		while (is_digit(c));

		lexer_ungetc(lex);
		*p++ = '\0';

		lex->tok.id = TOKEN_NUMBER;
		lex->tok.val = attr;
	}
	else if (c == '"') {

		c = lexer_getc(lex);

		while (c != '"' && c != '\n' && c != LEXER_EOF) {
			lexer_safe_store(lex, attr, &p, c);
			c = lexer_getc(lex);
		}

		if (c != '"') {
			*(attr + 16) = '\0';
			fprintf(stderr, "%s:%i: lexer: unclosed \"%s...\" string token\n",
					lex->fname, lex->line, attr);
			lex->btok = 1;
			lex->error++;
		}

		*p++ = '\0';

		lex->tok.id = TOKEN_STRING;
		lex->tok.val = attr;
	}
	else if (c == '{') {
		lex->tok.id = TOKEN_LEFT_BRACKET;
		lex->tok.val = NULL;
	}
	else if (c == '}') {
		lex->tok.id = TOKEN_RIGHT_BRACKET;
		lex->tok.val = NULL;
	}
	else if (c == '.') {
		lex->tok.id = TOKEN_DOT;
		lex->tok.val = NULL;
	}
	else if (c == '=') {
		lex->tok.id = TOKEN_EQUALITY;
		lex->tok.val = NULL;
	}
	else if (c == '+') {
		lex->tok.id = TOKEN_PLUS;
		lex->tok.val = NULL;
	}
	else if (c == '-') {
		lex->tok.id = TOKEN_MINUS;
		lex->tok.val = NULL;
	}
	else if (c == ';') {
		lex->tok.id = TOKEN_SEMICOLON;
		lex->tok.val = NULL;
	}
	else if (c == '#') {
		while (c != '\n' && c != LEXER_EOF) {
			c = lexer_getc(lex);
		}
		goto ltn_start;
	}
	else if (c == LEXER_EOF) {
		lex->tok.id = TOKEN_NULL;
		lex->tok.val = NULL;
	}
	else {
		if (lex->btok == 0) {
			fprintf(stderr, "%s:%i: lexer: unrecognized character \"%c\" so token will be skipped\n",
					lex->fname, lex->line, c);
			lex->btok = 1;
			lex->error++;
		}
		goto ltn_start;
	}

	if (lex->btok) {
		lex->tok.id = TOKEN_INVAL;
		lex->tok.val = NULL;
	}
}

